package cn.zyl.demo.common.tools;

import cn.zyl.demo.common.exception.ToolsException;

import java.io.*;
import java.util.*;

/**
 * PropertiesTool工具类说明:Properties配置文件读取工具，低频次使用
 *
 * @author wsz
 * @version v1.0
 * @date 2020-07-23
 */
public class PropertiesTool {
    private PropertiesTool() {
        throw new IllegalAccessError("PropertiesTool");
    }
    private static final String SUFFIX = ".properties";
    private static final String MSG_PATH_EMPTY = "properties file path is empty./文件路径为空.";
    private static final String MSG_PATH_NOT = "properties file is not found./文件未找到.";
    private static final String MSG_KEY_EMPTY = "key is empty./key为空.";
    private static final String MSG_FILE_EMPTY = "properties is empty./properties为空.";
    private static final String MSG_SUFFIX_ERROR = "properties Illegal suffix at end of file./文件结尾后缀不合法.";
    private static void checkPathKey(String path,String key) throws ToolsException {
        ToolsException.isTrueThrow(StringTool.isEmpty(path), MSG_PATH_EMPTY);
        ToolsException.isTrueThrow(StringTool.isEmpty(key), MSG_KEY_EMPTY);
    }
    /**
     *
     * @param path 待验证的路径
     * @param type true Properties方式；false ResourceBundle
     * @throws ToolsException 验证失败
     */
    private static void checkPath(String path,boolean type) throws ToolsException {
        ToolsException.isTrueThrow(StringTool.isEmpty(path), MSG_PATH_EMPTY);
        if(type) { ToolsException.isTrueThrow(!path.endsWith(SUFFIX), MSG_SUFFIX_ERROR); }
    }
    /**
     * <strong>项目内properties文件，加载Properties对象</strong>
     * <p>
     *     基于ClassLoder读取配置文件:<br>
     *     该方式只能读取类路径下的配置文件，有局限但是如果配置文件在类路径下比较方便.
     * </p>
     * @param path 文件路径.<pre>
     *  相对路劲，文件需在classpath目录下.</br>
     *  比如：config.properties在包com.test.config下,</br>
     *  路径就是com/test/config/config.properties</br>
     *  比如：config.properties在包src下,</br>
     *  路径就是config.properties</br>
     * </pre>
     * @return Properties对象
     * @throws ToolsException 文件读取异常
     */
    private static Properties loadProperties(String path) throws ToolsException {
        checkPath(path,true);
        Properties properties = new Properties();
        try {
            // 使用ClassLoader加载properties配置文件生成对应的输入流
            InputStream in = PropertiesTool.class.getClassLoader().getResourceAsStream(path);
            if(null == in){throw new ToolsException(MSG_PATH_NOT);}
            // 使用properties对象加载输入流
            properties.load(in);
            if(properties.isEmpty()){throw new ToolsException(MSG_FILE_EMPTY);}
        } catch (IOException e) {
            throw new ToolsException(e);
        }
        return properties;
    }
    /**
     * <strong>任意路径properties，加载Properties对象</strong>
     * <p>
     *     基于 InputStream 读取配置文件:<br>
     *     该方式的优点在于可以读取任意路径下的配置文件
     * </p>
     * @param path 文件路径.绝对路劲
     * @return Properties对象
     * @throws ToolsException 文件读取异常
     */
    private static Properties loadFreeProperties(String path) throws ToolsException {
        checkPath(path,true);
        Properties properties = new Properties();
        try {
            if(!new File(path).exists()){throw new ToolsException(MSG_PATH_NOT);}
            // 使用InPutStream流读取properties文件
            BufferedReader reader = new BufferedReader(new FileReader(path));
            // 使用properties对象加载输入流
            properties.load(reader);
            if(properties.isEmpty()){throw new ToolsException(MSG_FILE_EMPTY);}
        } catch (IOException e) {
            throw new ToolsException(e);
        }
        return properties;
    }
    /**
     * <strong>项目内properties文件，加载ResourceBundle对象</strong>
     * <p>
     *     通过 java.util.ResourceBundle 类来读取，这种方式比使用 Properties 要方便一些
     * </p>
     * @param path 文件路径.<pre>
     *  在使用中遇到的最大的问题可能是配置文件的路径问题，如果配置文件入在当前类所在的包下，</br>
     *  那么需要使用包名限定，如：config.properties入在com.test.config包下，</br>
     *  则要使用com/test/config/config.properties（通过Properties来获取）</br>
     *  或com/test/config/config（通过ResourceBundle来获取）;属性文件在src根目录下，</br>
     *  则直接使用config.properties或config即可
     * </pre>
     * @return Properties对象
     * @throws ToolsException 文件读取异常
     */
    private static ResourceBundle loadResource(String path) throws ToolsException {
        checkPath(path,false);
        ResourceBundle bundle = null;
        //通过 ResourceBundle.getBundle() 静态方法来获取（ResourceBundle是一个抽象类），
        // 这种方式来获取properties属性文件不需要加.properties后缀名，只需要文件名即可
        try {
            bundle = ResourceBundle.getBundle(path);
        } catch (Exception e) {
            throw new ToolsException(e);
        }
        return bundle;
    }
    /**
     * <strong>任意路径properties，加载ResourceBundle对象</strong>
     * <p>
     *     基于 InputStream 读取配置文件:<br>
     *     该方式的优点在于可以读取任意路径下的配置文件
     * </p>
     * @param path 文件路径.绝对路劲
     * @return ResourceBundle对象
     * @throws ToolsException 文件读取异常
     */
    private static ResourceBundle loadFreeResource(String path) throws ToolsException {
        checkPath(path,false);
        ResourceBundle bundle = null;
        try {
            path = path+SUFFIX;
            if(!new File(path).exists()){throw new ToolsException(MSG_PATH_NOT);}
            // 使用InPutStream流读取properties文件
            BufferedReader reader = new BufferedReader(new FileReader(path));
            bundle = new PropertyResourceBundle(reader);
        } catch (IOException e) {
            throw new ToolsException(e);
        }
        return bundle;
    }
    private static Map<String, String> getMap(ResourceBundle bundle){
        Set<String> keys = bundle.keySet();
        Map<String, String> map = new HashMap<>(keys.size()*2);
        for(String key:keys){
            map.put(key, bundle.getString(key));
        }
        return map;
    }
    private static Map<String, String> getMap(Properties properties){
        Enumeration<Object> ens = properties.keys();
        Map<String, String> map = new HashMap<>(properties.size()*2);
        String key;
        while (ens.hasMoreElements()){
            key = (String)ens.nextElement();
            map.put(key, properties.getProperty(key));
        }
        return map;
    }
    //-----------------------------
    /**
     * 项目内properties文件，根据key获取值
     * @param path 项目内相对路径。含后缀.properties，Properties加载；不含后缀.properties，ResourceBundle加载
     * @param key 获取的键
     * @return 值
     * @throws ToolsException 操作异常
     */
    public static String getValueByLocal(String path,String key) throws ToolsException{
        checkPathKey(path,key);
        if(path.endsWith(SUFFIX)){
            return loadProperties(path).getProperty(key);
        }
        return loadResource(path).getString(key);
    }
    /**
     * 任意properties文件，根据key获取值
     * @param path 任意绝对路径。含后缀.properties，Properties加载；不含后缀.properties，ResourceBundle加载
     * @param key 获取的键
     * @return 值
     * @throws ToolsException 操作异常
     */
    public static String getValueByFree(String path,String key) throws ToolsException{
        checkPathKey(path,key);
        if(path.endsWith(SUFFIX)){
            return loadFreeProperties(path).getProperty(key);
        }
        return loadFreeResource(path).getString(key);
    }
    /**
     * 项目内properties文件
     * @param path 项目内相对路径。含后缀.properties，Properties加载；不含后缀.properties，ResourceBundle加载
     * @return 键值对
     * @throws ToolsException 操作异常
     */
    public static Map<String, String> getMapByLocal(String path) throws ToolsException{
        ToolsException.isTrueThrow(StringTool.isEmpty(path), MSG_PATH_EMPTY);
        if(path.endsWith(SUFFIX)){
            return getMap(loadProperties(path));
        }
        return getMap(loadResource(path));
    }
    /**
     * 任意properties文件
     * @param path 任意绝对路径。含后缀.properties，Properties加载；不含后缀.properties，ResourceBundle加载
     * @return 键值对
     * @throws ToolsException 操作异常
     */
    public static Map<String, String> getMapByFree(String path) throws ToolsException{
        ToolsException.isTrueThrow(StringTool.isEmpty(path), MSG_PATH_EMPTY);
        if(path.endsWith(SUFFIX)){
            return getMap(loadFreeProperties(path));
        }
        return getMap(loadFreeResource(path));
    }

    public static void main(String[] args) {
        String key = "error.msg.regex.empty";
        System.out.println(getValueByLocal("i18n/common_msg.properties", key));
        System.out.println(getValueByFree("D:/common_msg.properties", key));
        System.out.println(getValueByLocal("i18n/common_msg", key));
        System.out.println(getValueByFree("D:/common_msg", key));
        System.out.println(getMapByLocal("i18n/common_msg"));
        System.out.println(getMapByFree("D:/common_msg"));
        System.out.println(getMapByLocal("i18n.common_msg"));
    }
}